﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DeepZoomPivotConstructor.VisualLib;
using DeepZoomPivotConstructor.VisualLib.Hotspots;
using DeepZoomPivotConstructor.VisualLib.Utilities;
using RenderEngineInterfaces;

namespace DirectoryRender
{
    [Export(typeof(IRenderItem))]
    [RenderInfo(typeof(DirectoryInfo))]
    public class DirectoryInfoRender : IRenderItem
    {
        public Task<Visual> Render(object what)
        {
            DirectoryInfo d = what as DirectoryInfo;
            return Task<Visual>.Factory.StartNew(() =>
            {
                return RenderDirectory(d);
            });
        }

        /// <summary>
        /// How much between slide boarders
        /// </summary>
        private double XBoarderSize { get; set; }
        /// <summary>
        /// How much between slide boarders
        /// </summary>
        private Double YBoarderSize { get; set; }

        /// <summary>
        /// The background color we should make the file...
        /// </summary>
        private Color FileBackgroundColor { get; set; }

        /// <summary>
        /// DPI to use when generating background with text - make it as small as possible
        /// to reduce memory
        /// </summary>
        private int DPIText { get; set; }

        /// <summary>
        /// How high should we make the text?
        /// </summary>
        private Double TextHeight { get; set; }

        public DirectoryInfoRender()
        {
            TextHeight = 0.5;
            YBoarderSize = 0.2;
            XBoarderSize = 0.2;
            DPIText = 150;
            FileBackgroundColor = Color.Gray;
        }

        /// <summary>
        /// The render engine so we can sub-render files and directories!
        /// </summary>
        [Import]
        private IRenderEngine MyRender { get; set; }

        /// <summary>
        /// Help with the layout of the images
        /// </summary>
        [Import]
        private ILayoutHelper MyLayout { get; set; }

        /// <summary>
        /// Returns a visual representing the contents of a directory.
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        private Visual RenderDirectory(DirectoryInfo dbase)
        {
            ///
            /// Get a list of all the files. We only want the non-null ones.
            /// 


            var visAsFile = from f in dbase.GetFiles()
                            select new FileWithTitle
                            {
                                FileSpec = f
                            };

            var visfiles = (from f in visAsFile
                            let rdr = MyRender.RenderToFuture(f)
                            where rdr != null
                            select rdr).ToArray();
            var directories = dbase.GetDirectories();
            var visdir = (from d in directories
                          let drdr = MyRender.RenderToFuture(d)
                          where drdr != null
                          select drdr).ToArray();

            Task<Visual>.WaitAll(visfiles);
            Task<Visual>.WaitAll(visdir);

            var goodRendersFiles = from v in visfiles.AsParallel()
                                   where v != null && v.Result != null
                                   select v.Result;

            var goodRendersDir = from d in new ListZipper<Task<Visual>, DirectoryInfo>(visdir, directories).AsParallel()
                                 where d._first.Result != null
                                 select AddTitleVisual(d._first.Result, d._second.FullName);

            var goodRenders = from v in new ListCombiner<Visual>(new IEnumerable<Visual>[] { goodRendersFiles, goodRendersDir })
                              orderby v.Info.Title ascending
                              select v;

            ///
            /// If nothing happened, then we return null.
            /// 

            VisualHolder allFileHolder = new VisualHolder();
            var completeArray = (from r in new VisualSeqenceWrapper(goodRenders, allFileHolder)
                                 select r).ToArray();
            if (completeArray.Length == 0)
            {
                return null;
            }
            if (completeArray.Length == 1)
            {
                completeArray = goodRenders.ToArray();
            }

            ///
            /// Now that we have everything, put it together into a big render!
            /// 

            Visual allFiles = MyLayout.LayoutIn2By3Rows(completeArray);
            allFileHolder.AddImage(allFiles);

            allFileHolder.Info = new Visual.VisualInfo();
            allFileHolder.Info.Title = dbase.Name;

            return allFileHolder;
        }

        /// <summary>
        /// If we should be using a different temp directory than the one that is there by default... :-)
        /// </summary>
        [Import("RenderingTempDir", AllowDefault = true)]
#pragma warning disable 0649
        private DirectoryInfo RenderingTempDir;
#pragma warning restore 0649

        /// <summary>
        /// Given a visual - it should have a top level title.
        /// Create a thing around it with a title
        /// </summary>
        /// <param name="visual"></param>
        /// <returns></returns>
        private Visual AddTitleVisual(Visual visual, string fullDirName)
        {
            ///
            /// Get the slides setup correctly
            /// 

            visual.ScaleToWidth(10.0);

            ///
            /// And create the title slide
            /// 

            VisualTitleAndBackground title = new VisualTitleAndBackground(visual)
            {
                {TextHeight, visual.Info.Title, new HSActionLink(new Uri("file://" + fullDirName))}
            };
            if (RenderingTempDir != null)
            {
                title.ImageWritingDirectory = RenderingTempDir;
            }

            title.Info = visual.Info;

            return title;
        }

        private class pair<T, U>
        {
            public T _first;
            public U _second;
        }

        private class ListZipper<T, U> : System.Collections.Generic.IEnumerable<pair<T, U>>
        {
            IEnumerable<T> _firstList;
            IEnumerable<U> _secondList;

            public ListZipper(IEnumerable<T> source1, IEnumerable<U> source2)
            {
                _firstList = source1;
                _secondList = source2;
            }

            #region IEnumerable<pair<T,U>> Members

            public IEnumerator<pair<T, U>> GetEnumerator()
            {
                var s1Enum = _firstList.GetEnumerator();
                var s2Enum = _secondList.GetEnumerator();

                while (s1Enum.MoveNext() && s2Enum.MoveNext())
                {
                    var r = new pair<T, U>();
                    r._first = s1Enum.Current;
                    r._second = s2Enum.Current;
                    yield return r;
                }
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            #endregion
        }

        private class ListCombiner<T> : System.Collections.Generic.IEnumerable<T>
        {
            /// <summary>
            /// Keep track of everything that we are to match!
            /// </summary>
            List<IEnumerable<T>> allItems = new List<IEnumerable<T>>();
            public ListCombiner(IEnumerable<T> source)
            {
                allItems.Add(source);
            }
            public ListCombiner(IEnumerable<T>[] sources)
            {
                foreach (var l in sources)
                {
                    allItems.Add(l);
                }
            }
            public ListCombiner(List<IEnumerable<T>> sources)
            {
                allItems.AddRange(sources);
            }

            /// <summary>
            /// Returns iterator code to move through everything, one by one.
            /// </summary>
            /// <returns></returns>
            public System.Collections.Generic.IEnumerator<T> GetEnumerator()
            {
                foreach (var l in allItems)
                {
                    foreach (var item in l)
                    {
                        yield return item;
                    }
                }
            }


            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            #endregion
        }

        /// <summary>
        /// We can render any directory info as long as it exists!
        /// </summary>
        /// <param name="what"></param>
        /// <returns></returns>
        public RenderPluginPriority RenderPriority(object what)
        {
            DirectoryInfo d = what as DirectoryInfo;
            d.Refresh();
            if (d.Exists)
            {
                return RenderPluginPriority.RPPGeneric;
            }
            return RenderPluginPriority.RPPCantDoIt;
        }
    }
}
